#ifndef FOUNDATION_DBMS_DBMS_H
#define FOUNDATION_DBMS_DBMS_H
#include <string>
#include <tuple>
#include <functional>
#include <SQLiteCpp/Database.h>
#include <SQLiteCpp/Transaction.h>
#include <utils/logger.h>
#include <utils/macro.h>
#include "dbms.pb.h"

namespace dbms {
template <class T> class Dbms {
public:
	Dbms(const std::string &dbFile, const std::string &tableName)
		: dbFile_(dbFile)
		, tableName_(tableName)
	{
	}
	bool Insert(const std::string &id, const T &t)
	{
		SQLite::Database db(
			dbFile_, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
		// Begin transaction
		SQLite::Transaction transaction(db);

		// Prepare query
		std::string sql = "INSERT INTO " + tableName_ + "(" +
						  SqlFromSchema("INSERT") + ")" + " VALUES (?,?)";
		SQLite::Statement insert{db, sql};
		insert.bind(1, id);
		insert.bind(2, Serialize(t));
		insert.exec();
		// Commit transaction
		transaction.commit();
		return true;
	}
	bool Update(const std::string &id, const T &t)
	{
		SQLite::Database db(
			dbFile_, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
		// Begin transaction
		SQLite::Transaction transaction(db);

		// Prepare query
		std::string sql = "UPDATE " + tableName_ + " SET " +
						  SqlFromSchema("UPDATE") + "=? WHERE id=?";
		SQLite::Statement insert{db, sql};
		insert.bind(1, id);
		insert.bind(2, Serialize(t));
		insert.exec();
		// Commit transaction
		transaction.commit();
		return true;
	}
	void GetAll(std::function<void(const T &)> getFunc)
	{
		SQLite::Database db(dbFile_);

		// Compile a SQL query, containing one parameter (index 1)
		std::string sql = "SELECT * FROM " + tableName_;
		SQLite::Statement query(db, sql);

		// Loop to execute the query step by step, to get rows of result
		while (query.executeStep()) {
			// Demonstrate how to get some typed column value
			const std::string id = query.getColumn(0);
			SQLite::Column data = query.getColumn(1);
			if (data.isNull()) {
				HTELINK_LOG_WARN("some db data error, table = %s, id = %s",
					tableName_.c_str(), id.c_str());
				continue;
			}
			std::string v = data.getString();
			T t = Deserialize(data);
			if (getFunc) {
				getFunc(t);
			}
		}
	}

	std::string FindEqual(
		const T &t,
		std::function<bool(const T &, const T &)> compareTo =
			[](const T &t1, const T &t2) -> bool {
			return Serialize(t1) == Serialize(t2);
		})
	{
		SQLite::Database db(
			dbFile_, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);

		// Compile a SQL query, containing one parameter (index 1)
		std::string sql = "SELECT * FROM " + tableName_;
		SQLite::Statement query(db, sql);

		// Loop to execute the query step by step, to get rows of result
		while (query.executeStep()) {
			// Demonstrate how to get some typed column value
			const std::string id = query.getColumn(0);
			SQLite::Column data = query.getColumn(1);
			if (data.isNull()) {
				continue;
			}
			T to = Deserialize(data.getString());
			if ((compareTo != nullptr)) {
				if (compareTo(t, to)) {
					return id;
				}
			}
		}
		return "";
	}
	void CreateTable(const std::string &table)
	{
		SQLite::Database db(
			dbFile_, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
		SQLite::Transaction transaction(db);

		std::string sql = "CREATE TABLE IF NOT EXISTS" + table + "(" +
						  SqlFromSchema("CREATE") + ");";
		db.exec(sql);

		transaction.commit();
	}

protected:
	std::string SqlFromSchema(const std::string &schema)
	{
		if (schema == "CTEATE") {
			return std::string("id TEXT PRIMARY KEY,data TEXT");
		} else if (schema == "INSERT") {
			return std::string("id,data");
		} else if (schema == "UPDATE") {
			return std::string("data");
		}
		return std::string();
	}
	virtual std::string Serialize(const T &t) = 0;
	virtual T Deserialize(const std::string &data) = 0;

private:
	std::string dbFile_;
	std::string tableName_;
};
} // namespace dbms
#endif